/*
 * 
 * Copyright 2010 David H Pollack
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 * 
 * 
 */

package com.dhpollack.rotogss.servlets;

import static com.google.appengine.api.labs.taskqueue.TaskOptions.Builder.url;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

import javax.jdo.JDOCanRetryException;
import javax.jdo.JDOObjectNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;

import net.sf.jsr107cache.Cache;
import net.sf.jsr107cache.CacheException;
import net.sf.jsr107cache.CacheManager;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.dhpollack.rotogss.data.RotoGSSLeagueXml;
import com.dhpollack.rotogss.data.YahooGames;
import com.dhpollack.rotogss.data.YahooGamesStats;
import com.dhpollack.rotogss.data.YahooLeagueTeamStandings;
import com.dhpollack.rotogss.data.YahooLeagueTeams;
import com.dhpollack.rotogss.data.YahooLeagues;
import com.dhpollack.rotogss.helper.PMF;
import com.dhpollack.rotogss.helper.XPATH;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.labs.taskqueue.Queue;
import com.google.appengine.api.labs.taskqueue.QueueFactory;

/**
 * @author davidpl
 *
 */
public class YStoreTeamStandingServlet extends HttpServlet {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = -9168270688637879539L;

	private Integer game;
	
	private String league;
	
	private String league_update;
	
	private String league_type;
	
	private static XPath xPath = XPATH.get().newXPath();
	
	private static Queue queue = QueueFactory.getDefaultQueue();
	
	private static final int RETRY_TASK = 5; 
	
	protected void service(HttpServletRequest req, HttpServletResponse resp) 
    throws ServletException, IOException {
		
		Integer retryVal = (req.getParameter("retry") == null) ? 0 : Integer.valueOf(req.getParameter("retry"));
		
		league = req.getParameter("league");
		
		if(league == null) {
			log("No league set");
			return;
		} else {
			log("Processing stats for " + league);
		}
		boolean force_update = false;
		if(req.getParameter("force_update") != null) {
			force_update = (req.getParameter("force_update").toLowerCase().equals("true")) ?
									true :
									false;
		}
		
		// Processing for game key.  Will be replaced with game parse
		String regex = "\\.";
		Pattern numSplit = Pattern.compile(regex);
		String[] m = numSplit.split(league);
		game = Integer.parseInt(m[0]);
		
		RotoGSSLeagueXml xml = getLeagueXmlFromApp();
		
		//Date league_update = xml.getUpdateTimestamp();
		String league_xml = xml.getLeagueXml();
		NodeList team_nodes = parseLeagueXml(league_xml, "league/standings/teams/team");
		if(!league_type.equals("roto")) {
			log(league_type + " not supported");
			return;
		}
		int team_count = team_nodes.getLength();
		try {
			if(!checkLastStanding(team_nodes.item(team_count - 1)) || force_update) {
				for(int i=0;i<team_count;i++) {
					Key standing_key = saveStandingToDatastore(team_nodes.item(i), force_update);
					log(standing_key.toString());
					req.setAttribute(standing_key.toString(), standing_key);
				}				
			} else if (retryVal < RETRY_TASK) {
				retryVal++;
                queue.add(url(req.getServletPath()).param("league", league)
           		     .param("force_update", String.valueOf(force_update))
           		     .param("retry", retryVal.toString())
           		     .countdownMillis((long) (3.5*60*60*1000)));

			} else {
				log("This task retried " + retryVal + " times");
			}
		} catch(XPathExpressionException e) {
			
		}

		

	}
	
	private boolean checkLastStanding(Node node)
	throws XPathExpressionException {
		// TODO Auto-generated method stub
		boolean bool = false;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		String team_key = (String)xPath.evaluate("team_key", node, XPathConstants.STRING);
		Key key = new KeyFactory.Builder(YahooGames.class.getSimpleName(), game)
		.addChild(YahooLeagues.class.getSimpleName(), league)
		.addChild(YahooLeagueTeams.class.getSimpleName(), team_key)
		.addChild(YahooLeagueTeamStandings.class.getSimpleName(), league_update)
		.getKey();
		try {
			pm.getObjectById(YahooLeagueTeamStandings.class, key);
			bool = true;
			log("Found last standing");
		} catch(JDOObjectNotFoundException e) {
			bool = false;
			log("Last standing not found");
		}
		pm.close();
		return bool;

		
	}

	private RotoGSSLeagueXml getLeagueXmlFromApp() {
        Cache cache = null;

        try {
            cache = CacheManager.getInstance().getCacheFactory().createCache(Collections.emptyMap());
        } catch (CacheException e) {
            // ...
        	log("Memcache creation failed");
        }
        
        String memcache_key = league + "-xml";
        RotoGSSLeagueXml xml = null;
        
        if(cache != null && cache.containsKey(memcache_key)) {
        	log("Using Memcache");
        	xml = (RotoGSSLeagueXml) cache.get(memcache_key);
        }
        if(xml == null) {
        	log("Using Datastore");
            PersistenceManager pm = PMF.get().getPersistenceManager();
            Key key = new KeyFactory.Builder(RotoGSSLeagueXml.class.getSimpleName(), league).getKey();
            try {
            	xml = pm.getObjectById(RotoGSSLeagueXml.class, key);
            } catch(JDOObjectNotFoundException e) {
            	log("LeagueXml not found in datastore");
            	e.printStackTrace();
            }
        }
		return xml;
	}

	private NodeList parseLeagueXml(String leagues_xml, String expression) {
		NodeList n = null;
		try {
			InputSource is = new InputSource(new StringReader(leagues_xml));
			Document response = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
			setLeagueVars(response);
			n = (NodeList)xPath.evaluate( expression , response, XPathConstants.NODESET);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (XPathExpressionException e) {
			e.printStackTrace();
		}
		return n;
	}
	
	private void setLeagueVars(Document doc)
	throws XPathExpressionException {
		league_update = (String) xPath.evaluate( "/league/league_update_timestamp" , doc, XPathConstants.STRING);
		league_type = (String) xPath.evaluate( "/league/settings/scoring_type" , doc, XPathConstants.STRING);
	}
	
	private Key saveStandingToDatastore(Node node, boolean force_update)
	throws XPathExpressionException {
		// TODO Auto-generated method stub
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Transaction tx = pm.currentTransaction();
		
		String team_key = (String)xPath.evaluate("team_key", node, XPathConstants.STRING);

		Key key = new KeyFactory.Builder(YahooGames.class.getSimpleName(), game)
								.addChild(YahooLeagues.class.getSimpleName(), league)
								.addChild(YahooLeagueTeams.class.getSimpleName(), team_key)
								.addChild(YahooLeagueTeamStandings.class.getSimpleName(), league_update)
								.getKey();
		Key league_key = key.getParent().getParent();
		
		Double team_points = Double.valueOf((String)xPath.evaluate("team_points/total", node, XPathConstants.STRING));
		Double team_points_change = Double.valueOf((String)xPath.evaluate("team_standings/points_change", node, XPathConstants.STRING));
		Integer team_rank = Integer.parseInt((String)xPath.evaluate("team_standings/rank", node, XPathConstants.STRING));

		
		Date timestamp = new Date();
		timestamp.setTime(Long.valueOf(league_update) * 1000);
		YahooLeagueTeamStandings ylts;
		try {
			int NUM_TRANS_RETRIES = 8;
			for(int i = 0 ; i < NUM_TRANS_RETRIES; i++) {
				tx.begin();
				Map<List<Key>, List<Double>> stats = parseStandingStats(node);
				try {
					ylts = pm.getObjectById(YahooLeagueTeamStandings.class, key);
					log("Standing already found. Overwriting datastore: " + force_update);
				} catch(JDOObjectNotFoundException e) {
					log("Creating new standing entity with key " + key.getName());
					ylts = new YahooLeagueTeamStandings();
					ylts.setStandingsKey(key);
					force_update = true;
				}
				if(force_update) {
					ylts.setTeamPoints(team_points);
					ylts.setTeamPointsChange(team_points_change);
					ylts.setTeamRank(team_rank);
					ylts.setTimestamp(timestamp);
					ylts.setStatKeys(stats.keySet().iterator().next());
					ylts.setStatValues(stats.values().iterator().next());
					ylts.setLeagueKey(league_key);
					pm.makePersistent(ylts);
				}
				try {
					tx.commit();
					break;
				} catch(JDOCanRetryException e) {
					log("Transaction failed. Restart number: " + (i+1));
	                if (i == (NUM_TRANS_RETRIES - 1)) { 
	                    throw e;
	                }
	            }
			}
		} finally {
			if(tx.isActive()) {
				tx.rollback();
			}
		}

		return key;
	}

	private Map<List<Key>, List<Double>> parseStandingStats(Node node)
	throws XPathExpressionException {
		List<Key> stat_keys = new ArrayList<Key>();
		List<Double> stat_values = new ArrayList<Double>();
		Map<List<Key>, List<Double>> r = new HashMap<List<Key>, List<Double>>();
		// TODO Auto-generated method stub
    	NodeList stat_nodes = (NodeList)xPath.evaluate("team_stats/stats/stat", node, XPathConstants.NODESET);
    	int stat_count = stat_nodes.getLength();
    	XPathExpression stat_id_exp = xPath.compile("stat_id");
    	XPathExpression value_exp = xPath.compile("value");
    	for(int i=0;i<stat_count;i++) {
    		Integer stat_id = Integer.parseInt((String)stat_id_exp.evaluate(stat_nodes.item(i), XPathConstants.STRING));
        	Double stat_value = Double.valueOf((String)value_exp.evaluate(stat_nodes.item(i), XPathConstants.STRING));
    		
        	Key stat_key = new KeyFactory.Builder(YahooGames.class.getSimpleName(), game)
        								 .addChild(YahooGamesStats.class.getSimpleName(), game+"-"+stat_id)
        								 .getKey();
        	stat_keys.add(stat_key);
        	stat_values.add(stat_value);
    	}

    	r.put(stat_keys, stat_values);
		return r;
	}



}
